/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.ipti.br.tag.dao;

import org.ipti.br.tag.bean.dataManipulation.HumanHbMeasure;
import org.ipti.br.tag.bean.dataManipulation.SelectionElement;
import org.ipti.br.tag.entity.*;
import org.ipti.br.tag.entity.enums.PersonageType;
import org.ipti.br.tag.entity.enums.Reference;
import org.ipti.br.tag.entity.enums.Sex;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Map;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.persistence.criteria.*;

/**
 *
 * @author israel
 */
@Stateless
public class HumanFacade extends AbstractFacade<Human> {

    @PersistenceContext(unitName = "TAG2PU")
    private EntityManager em;

    @Override
    protected EntityManager getEntityManager() {
        return em;
    }

    public HumanFacade() {
        super(Human.class);
    }

    @Override
    public void create(Human human) {
        
        List<AdditionalInfo> additionalInfos = new ArrayList<AdditionalInfo>();
        for(AdditionalInfo info : human.getAdditionalInfos()){
            if(!(info.getValue().isEmpty() || info.getValue() == null))
                additionalInfos.add(info);
        }
        human.setAdditionalInfos(additionalInfos);
        
        getEntityManager().persist(human);
    }
    
    public void syncCreate(Human human){
        getEntityManager().persist(human);
    }

    @Override
    public void edit(Human human) {
        getEntityManager().merge(human);
    }
    
    public void detach(Human human){
        getEntityManager().detach(human);
    }

    public List<Human> searchHumanPerName(String humanName) {
        return searchLimitedHumanPerName(humanName, null, null);
    }

    public List<Human> searchHumanMalePerName(String query) {
        return searchLimitedHumanPerName(query, null, Sex.MALE);
    }

    public List<Human> searchHumanFemalePerName(String query) {
        return searchLimitedHumanPerName(query, null, Sex.FEMALE);
    }

    public List<Human> searchLimitedHumanPerName(String query, Integer limit, Sex sex) {
        CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
        CriteriaQuery<Human> cq = cb.createQuery(Human.class);
        Root<Human> root = cq.from(Human.class);
        Predicate p = cb.like(cb.upper(cb.concat(root.get(Human_.firstName), cb.concat(" ", root.get(Human_.lastName)))), "%" + query.toUpperCase() + "%");
        p = (sex != null) ? cb.and(cb.equal(root.get(Human_.sex), sex),p) : p;
        cq.where(p);
        cq.orderBy(cb.asc(root.get(Human_.firstName)),cb.asc(root.get(Human_.lastName)));

        Query q = getEntityManager().createQuery(cq);
        if(limit != null)
            q.setMaxResults(limit);
        return q.getResultList();
    }

    public List<String> searchHumanNames(String query, Integer limit, Sex sex) {
        CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
        CriteriaQuery<String> cq = cb.createQuery(String.class);
        Root<Human> root = cq.from(Human.class);
        
        cq.select(cb.concat(root.get(Human_.firstName),cb.concat(" ", root.get(Human_.lastName))));
       
        Predicate p = cb.like(cb.upper(cb.concat(root.get(Human_.firstName), cb.concat(" ", root.get(Human_.lastName)))), "%" + query.toUpperCase() + "%");
        p = (sex != null) ? cb.and(cb.equal(root.get(Human_.sex), sex),p) : p;
        cq.where(p);
        cq.orderBy(cb.asc(root.get(Human_.firstName)),cb.asc(root.get(Human_.lastName)));

        Query q = getEntityManager().createQuery(cq);
        if(limit != null)
            q.setMaxResults(limit);
        return q.getResultList();
    }

    public List<Human> searchAllocatedHuman(String query,Personage personage, Unity unity, Integer limit) {
        CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
        CriteriaQuery<Human> cq = cb.createQuery(Human.class);
        Root<Human> root = cq.from(Human.class);
        Join<Human, Actor> actors = root.join(Human_.actors);

        Predicate p = cb.like(cb.upper(cb.concat(root.get(Human_.firstName), root.get(Human_.lastName))), "%" + query.toUpperCase() + "%");
        Predicate p1 = cb.equal(actors.get(Actor_.unity).get(Unity_.id), unity.getId());
        Predicate p2 = cb.equal(actors.get(Actor_.personage).get(Personage_.id), personage.getId());
        Predicate p3 = cb.equal(actors.join(Actor_.personage).get(Personage_.personageType), PersonageType.ALLOCATED);

        cq.where(cb.and(p,p1,p2));
        cq.distinct(true);
        cq.orderBy(cb.asc(root.get(Human_.firstName)),
                cb.asc(root.get(Human_.lastName)));

        Query q = getEntityManager().createQuery(cq).setMaxResults(limit);
        return q.getResultList();
    }

    public List<Human> modifiedHumanBetweenDate(Calendar initialDate, Calendar finalDate, Long serialNumber) {
        CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
        CriteriaQuery<Human> cq = cb.createQuery(Human.class);
        Root<Human> from = cq.from(Human.class);
        Predicate predicate1 = cb.notEqual(from.get(Human_.serialNumber), serialNumber);
        Predicate predicate2 = cb.greaterThanOrEqualTo(from.get(Human_.lastModified), initialDate);
        Predicate predicate3 = cb.lessThan(from.get(Human_.lastModified), finalDate);
        cq.where(cb.and(predicate1, predicate2, predicate3));

        return getEntityManager().createQuery(cq).getResultList();
    }

    public List<Human> findAllOrder() {
        CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
        CriteriaQuery<Human> cq = cb.createQuery(Human.class);
        Root<Human> root = cq.from(Human.class);
        cq.orderBy(cb.asc(root.get(Human_.firstName)),
                cb.asc(root.get(Human_.lastName)));
        return getEntityManager().createQuery(cq).getResultList();
    }

    public List<Human> searchHumanPerPersonageUnityName(String humanName, Long unityID, Long personageID) {
        CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
        CriteriaQuery<Human> cq = cb.createQuery(Human.class);
        Root<Human> root = cq.from(Human.class);
        Join<Human, Actor> actors = root.join(Human_.actors);
        cq.where(cb.and(
                cb.like(cb.upper(cb.concat(root.get(Human_.firstName), root.get(Human_.lastName))), "%" + humanName.toUpperCase() + "%"),
                cb.equal(actors.join(Actor_.unity).get(Unity_.id), unityID),
                cb.equal(actors.join(Actor_.personage).get(Personage_.id), personageID)));


        return getEntityManager().createQuery(cq).getResultList();
    }

    public List<Human> searchHumanPerPersonageUnity(Long unityID, Long personageID) {
        CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
        CriteriaQuery<Human> cq = cb.createQuery(Human.class);
        Root<Human> root = cq.from(Human.class);
        Join<Human, Actor> actors = root.join(Human_.actors);
        cq.where(cb.and(
                cb.equal(actors.join(Actor_.unity).get(Unity_.id), unityID),
                cb.equal(actors.join(Actor_.personage).get(Personage_.id), personageID)));


        return getEntityManager().createQuery(cq).getResultList();
    }
    
        
    public List<Human> searchHuman(Long unityID) {
        CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
        CriteriaQuery<Human> cq = cb.createQuery(Human.class);
        Root<Human> root = cq.from(Human.class);
        Predicate p1 = cb.equal( root.join(Human_.actors).join(Actor_.unity).get(Unity_.id), unityID);
        cq.where(p1);

        try{
            return getEntityManager().createQuery(cq).getResultList();
        }catch(NoResultException e){
//            e.printStackTrace();
            return null;
        }

    }

    public List<Human> findAllOrder(Sex sex, int first, int pageSize, Map<String, String> filters) {
        CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
        CriteriaQuery<Human> cq = cb.createQuery(Human.class);
        Root<Human> root = cq.from(Human.class);

        List<Predicate> predicateList = new ArrayList<Predicate>();
        if(sex != null) {
            predicateList.add(cb.equal(root.get(Human_.sex), sex));
        }
        
        for (String filter : filters.keySet()) {
            Predicate p = null;
            if (filter.equals("name")) {
                p = cb.like(cb.upper(cb.concat(root.get(Human_.firstName), root.get(Human_.lastName))), "%" + filters.get(filter).toUpperCase() + "%");
                predicateList.add(p);
            }
        }

        if (!predicateList.isEmpty()) {
            if (predicateList.size() == 1) {
                cq.where(cb.and(predicateList.get(0)));
            } else {
                cq.where(cb.and(predicateList.toArray(new Predicate[0])));
            }
        }

        cq.orderBy(cb.asc(root.get(Human_.firstName)),
                cb.asc(root.get(Human_.lastName)));
        Query q = getEntityManager().createQuery(cq);
        q.setFirstResult(first);
        q.setMaxResults(pageSize);
        return q.getResultList();
    }

    public int getHumanCount(Sex sex) {
        CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
        CriteriaQuery cq = cb.createQuery();
        Root<Human> root = cq.from(Human.class);
        cq.select(cb.count(root));
        
        if(sex != null) {
            cq.where(cb.equal(root.get(Human_.sex), sex));
        }

        return ((Long) getEntityManager().createQuery(cq).getSingleResult()).intValue();
    }
    
    //humanos com ator ativos
    public List<Human> searchHumanWithActorActivatedPerPersonageUnities(List<Long> unities, Long personageID,
            int first, int pageSize, Map<String, String> filters) {
        CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
        CriteriaQuery<Human> cq = cb.createQuery(Human.class);
        Root<Human> root = cq.from(Human.class);
        Join<Human, Actor> actors = root.join(Human_.actors);
        cq.distinct(true);

        List<Predicate> unitiesPredicateList = new ArrayList<Predicate>();
        for (Long l : unities) {
            unitiesPredicateList.add(cb.equal(actors.join(Actor_.unity).get(Unity_.id), l));
        }
        
        List<Predicate> predicateList = new ArrayList<Predicate>();
        predicateList.add(cb.equal(actors.join(Actor_.personage).get(Personage_.id), personageID));
        predicateList.add(cb.lessThanOrEqualTo(actors.get(Actor_.activated), Calendar.getInstance()));

        String filter = filters.get("name");
        if(filter != null && !filter.isEmpty()) {
            Predicate p = cb.like(cb.upper(cb.concat(root.get(Human_.firstName), root.get(Human_.lastName))), "%" + filter.toUpperCase() + "%");
            predicateList.add(p);
        }

        if (unitiesPredicateList.isEmpty()) {
            return new ArrayList<Human>();
        } else if (unitiesPredicateList.size() == 1) {
            cq.where(
                    cb.and(predicateList.toArray(new Predicate[0])),
                    cb.or(
                    cb.greaterThanOrEqualTo(actors.get(Actor_.deactivated), Calendar.getInstance()),
                    cb.isNull(actors.get(Actor_.deactivated))),
                    unitiesPredicateList.get(0));
        } else {
            cq.where(cb.and(predicateList.toArray(new Predicate[0])),
                    cb.or(
                        cb.greaterThanOrEqualTo(actors.get(Actor_.deactivated), Calendar.getInstance()),
                        cb.isNull(actors.get(Actor_.deactivated))),
                    cb.or(unitiesPredicateList.toArray(new Predicate[0])));
        }
        
        cq.orderBy(cb.asc(root.get(Human_.firstName)),
                cb.asc(root.get(Human_.lastName)));
        
        Query q = getEntityManager().createQuery(cq);
        q.setFirstResult(first);
        q.setMaxResults(pageSize);
        return q.getResultList();
    }
    
    //humanos com ator ativos
    public List<Human> searchHumanWithActivatedActor(List<Unity> unities, Personage personage) {
        if(unities.isEmpty()) {return new ArrayList<Human>();}
        
        CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
        CriteriaQuery<Human> cq = cb.createQuery(Human.class);
        Root<Human> root = cq.from(Human.class);
        Join<Human, Actor> actors = root.join(Human_.actors);
        cq.distinct(true);

        List<Predicate> unitiesPredicateList = new ArrayList<Predicate>();
        for (Unity u : unities) {
            unitiesPredicateList.add(cb.equal(actors.join(Actor_.unity).get(Unity_.id), u.getId()));
        }
        
        Predicate p1 = cb.equal(actors.join(Actor_.personage).get(Personage_.id), personage.getId());
        Predicate p2 = cb.lessThanOrEqualTo(actors.get(Actor_.activated), Calendar.getInstance());

        cq.where(
                cb.and(p1,p2),
                cb.or(
                    cb.greaterThanOrEqualTo(actors.get(Actor_.deactivated), Calendar.getInstance()),
                    cb.isNull(actors.get(Actor_.deactivated))),
                cb.or((Predicate[]) unitiesPredicateList.toArray()));
        
        cq.orderBy(cb.asc(root.get(Human_.firstName)),
                cb.asc(root.get(Human_.lastName)));
        
        Query q = getEntityManager().createQuery(cq);
        return q.getResultList();
    }
    
    //humanos com ator ativos
    public List<Human> searchHumanWithActivatedActor(List<UnitySchedule> unitySchedules) {
        if(unitySchedules.isEmpty()) {return new ArrayList<Human>();}
        
        CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
        CriteriaQuery<Human> cq = cb.createQuery(Human.class);
        Root<Human> root = cq.from(Human.class);
        Join<Human, Actor> actors = root.join(Human_.actors);
        Join<Actor, ActorSchedule> actorSchedules = actors.join(Actor_.actorSchedules);
        cq.distinct(true);

        List<Predicate> predicateList = new ArrayList<Predicate>();
        for (UnitySchedule us : unitySchedules) {
            predicateList.add(cb.equal(actorSchedules.get(ActorSchedule_.referenceID), us.getId()));
        }

        Predicate p1 = cb.lessThanOrEqualTo(actors.get(Actor_.activated), Calendar.getInstance());
        Predicate p2 = cb.equal(actorSchedules.get(ActorSchedule_.reference), Reference.UNITY);

        cq.where(
                cb.and(p1,p2),
                cb.or(
                    cb.greaterThanOrEqualTo(actors.get(Actor_.deactivated), Calendar.getInstance()),
                    cb.isNull(actors.get(Actor_.deactivated))
                ),
                cb.or((Predicate[]) predicateList.toArray()));

        cq.orderBy(cb.asc(root.get(Human_.firstName)),
                cb.asc(root.get(Human_.lastName)));
        Query q = getEntityManager().createQuery(cq);
        return q.getResultList();
    }
    
    public List<Human> getActivatedHumans(Unity unity) {
        CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
        CriteriaQuery<Human> cq = cb.createQuery(Human.class);
        Root<Human> rt = cq.from(Human.class);
        Join<Human, Actor> actors = rt.join(Human_.actors);
        
        Calendar c = Calendar.getInstance();
        
        Predicate p2 = cb.equal(actors.join(Actor_.unity).get(Unity_.id), unity.getId());
        Predicate p3 = cb.lessThanOrEqualTo(actors.get(Actor_.activated), c);
        Predicate p4 = cb.or(
                    cb.greaterThanOrEqualTo(actors.get(Actor_.deactivated), c),
                    cb.isNull(actors.get(Actor_.deactivated)));
        cq.where(p2,p3,p4);

        cq.distinct(true);
        cq.orderBy(cb.asc(rt.get(Human_.firstName)),
                cb.asc(rt.get(Human_.lastName)));
        Query q = getEntityManager().createQuery(cq);
        return q.getResultList();
    }
    
    public int getHumanWithActorActivatedPerPersonageUnitiesCount(List<Long> unities, Long personageID) {
        CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
        CriteriaQuery cq = cb.createQuery();
        Root<Human> root = cq.from(Human.class);
        Join<Human, Actor> actors = root.join(Human_.actors);
        cq.distinct(true);
        cq.select(cb.count(root));
        
        
        List<Predicate> unitiesPredicateList = new ArrayList<Predicate>();
        for (Long l : unities) {
            unitiesPredicateList.add(cb.equal(actors.join(Actor_.unity).get(Unity_.id), l));
        }
        
        if (unitiesPredicateList.isEmpty()) {
            return 0;
        } else if (unitiesPredicateList.size() == 1) {
            cq.where(
                    cb.and(
                    cb.equal(actors.join(Actor_.personage).get(Personage_.id), personageID),
                    cb.lessThanOrEqualTo(actors.get(Actor_.activated), Calendar.getInstance())),
                    cb.or(
                    cb.greaterThanOrEqualTo(actors.get(Actor_.deactivated), Calendar.getInstance()),
                    cb.isNull(actors.get(Actor_.deactivated))),
                    unitiesPredicateList.get(0));
        } else {
            cq.where(cb.and(
                    cb.equal(actors.join(Actor_.personage).get(Personage_.id), personageID),
                    cb.lessThanOrEqualTo(actors.get(Actor_.activated), Calendar.getInstance())),
                    cb.or(
                    cb.greaterThanOrEqualTo(actors.get(Actor_.deactivated), Calendar.getInstance()),
                    cb.isNull(actors.get(Actor_.deactivated))),
                    cb.or(unitiesPredicateList.toArray(new Predicate[0])));
        }
        return ((Long) getEntityManager().createQuery(cq).getSingleResult()).intValue();
    }
    
    //retorna pessoas da unidade e se podem participar do HB ou não
    public List<SelectionElement<Human,Boolean>> listAuthorizedStatus(Campaign campaign, Unity unity) {
        Class selectionClass = (new SelectionElement<Human,Boolean>()).getClass();
        
        CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
        CriteriaQuery<SelectionElement<Human,Boolean>> cq = cb.createQuery(selectionClass);
        Root<Human> rt = cq.from(Human.class);
        
        Predicate p = cb.equal(rt.join(Human_.actors).get(Actor_.unity).get(Unity_.id), unity.getId());
        
        Subquery<Long> subQ = cq.subquery(Long.class);
        Root<Hb> subRt = subQ.from(Hb.class);
        subQ.select(subRt.get(Hb_.id));
        subQ.where(cb.equal(subRt.get(Hb_.campaign).get(Campaign_.id), campaign.getId()),
                cb.equal(subRt.get(Hb_.human).get(Human_.id), rt.get(Human_.id)),
                cb.equal(subRt.get(Hb_.ordernation), 1));
        
        cq.select(cb.construct(selectionClass, rt, cb.exists(subQ))).where(p);
        
//        cq.distinct(true);
        cq.orderBy(cb.asc(rt.get(Human_.firstName)), cb.asc(rt.get(Human_.lastName)));
        return getEntityManager().createQuery(cq).getResultList();
    }
    
    //retorna anêmicos da unidade e se já compareceram a consulta ou não
    public List<SelectionElement<Human,Boolean>> listAnemicsStatus(Campaign campaign, Unity unity) {
        Class selectionClass = (new SelectionElement<Human,Boolean>()).getClass();
        
        CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
        CriteriaQuery<SelectionElement<Human,Boolean>> cq = cb.createQuery(selectionClass);
        Root<Human> rt = cq.from(Human.class);
        
        //Apenas atores da unidade
        Predicate p = cb.equal(rt.join(Human_.actors).get(Actor_.unity).get(Unity_.id), unity.getId());
        
        //Subconsulta para saber se já tem HB2 ou HB3
        Subquery<Long> subQ = cq.subquery(Long.class);
        Root<Hb> subRt = subQ.from(Hb.class);
        subQ.select(subRt.get(Hb_.id));
        subQ.where(cb.equal(subRt.get(Hb_.campaign).get(Campaign_.id), campaign.getId()),
                cb.equal(subRt.get(Hb_.human).get(Human_.id), rt.get(Human_.id)),
                cb.ge(subRt.get(Hb_.ordernation), 2));
        
        //Subconsulta para saber se esta Anemico no HB1
        Subquery<Long> subQ2 = cq.subquery(Long.class);
        Root<Hb> subRt2 = subQ2.from(Hb.class);
        subQ2.select(subRt2.get(Hb_.id));
        subQ2.where(cb.equal(subRt2.get(Hb_.campaign).get(Campaign_.id), campaign.getId()),
                cb.equal(subRt2.get(Hb_.human).get(Human_.id), rt.get(Human_.id)),
                cb.equal(subRt2.get(Hb_.anemic), true),
                cb.equal(subRt2.get(Hb_.ordernation), 1));
        
        
        cq.select(cb.construct(selectionClass, rt, cb.exists(subQ))).where(p, cb.exists(subQ2));
        cq.distinct(true);
        cq.orderBy(cb.asc(rt.get(Human_.firstName)), cb.asc(rt.get(Human_.lastName)));
        return getEntityManager().createQuery(cq).getResultList();
    }
    
    public List<Human> listAnemics(Unity unity) {
        CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
        CriteriaQuery<Human> cq = cb.createQuery(Human.class);
        Root<Human> rt = cq.from(Human.class);
//        Join<Hb, Human> human = rt.join(Hb_.human);
        
//        cq.select(rt.get(Hb_.human));
        
        Subquery<Long> subQ = cq.subquery(Long.class);
        Root<Hb> subRt = subQ.from(Hb.class);
        subQ.select(subRt.get(Hb_.id));
        subQ.where(cb.equal(subRt.get(Hb_.anemic), true),
                cb.equal(subRt.get(Hb_.human).get(Human_.id), rt.get(Human_.id)),
                cb.ge(subRt.get(Hb_.ordernation), 1));
        Predicate p1 = cb.equal(rt.join(Human_.actors).get(Actor_.unity).get(Unity_.id), unity.getId());
//        Predicate p2 = cb.equal(rt.get(Hb_.anemic), true);
        
//        cq.where(p1,p2);
        cq.where(p1,cb.exists(subQ));
        cq.orderBy(cb.asc(rt.get(Human_.firstName)),cb.asc(rt.get(Human_.lastName)));
        cq.distinct(true);
        
        return getEntityManager().createQuery(cq).getResultList();
    }
    
    public Long countHumans(Unity unity){
        CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
        CriteriaQuery<Long> cq = cb.createQuery(Long.class);
        Root<Human> root = cq.from(Human.class);
        Join<Human, Actor> actors = root.join(Human_.actors);

        cq.select(cb.count(root));
        
        Predicate p1 = cb.equal(actors.get(Actor_.unity).get(Unity_.id), unity.getId());

        cq.where(p1);
        cq.distinct(true);

        Query q = getEntityManager().createQuery(cq);
        
        return (Long) q.getSingleResult();
    } 
    

    
}
